package com.lzh.redis.advice;

import com.lzh.redis.annotation.DistributedLock;
import com.lzh.redis.config.RedissonConfig;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

/**
 * @author zhehen.lu
 * @date 2025/9/6 11:25
 */
@Aspect
@ConditionalOnBean({RedissonClient.class})
@AutoConfigureAfter({RedissonConfig.class})
@Order(2147483637)
public class DistributedLockAdvice {
    private static final Logger log = LoggerFactory.getLogger(DistributedLockAdvice.class);
    @Autowired
    private RedissonClient redissonClient;
    private SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
    private DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();

    public DistributedLockAdvice() {
    }

    @Pointcut("@annotation(com.lzh.redis.annotation.DistributedLock)")
    public void lockPointCut() {
    }

    @Around("lockPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature)point.getSignature();
        DistributedLock lockAnnotation = (DistributedLock)signature.getMethod().getAnnotation(DistributedLock.class);
        String key = this.getValueBySpEL(lockAnnotation.key(), signature, point.getArgs());
        RLock rLock = this.redissonClient.getLock(key);

        Object object;
        try {
            if (lockAnnotation.time() > 0L) {
                rLock.lock(lockAnnotation.time(), lockAnnotation.timeUint());
            } else {
                rLock.lock();
            }

            object = point.proceed();
        } finally {
            rLock.unlock();
        }

        return object;
    }

    private String getValueBySpEL(String spEL, MethodSignature methodSignature, Object[] args) {
        String[] paramNames = this.nameDiscoverer.getParameterNames(methodSignature.getMethod());
        if (paramNames != null && paramNames.length > 0) {
            Expression expression = this.spelExpressionParser.parseExpression(spEL);
            EvaluationContext context = new StandardEvaluationContext();

            for(int i = 0; i < args.length; ++i) {
                context.setVariable(paramNames[i], args[i]);
            }

            return expression.getValue(context).toString();
        } else {
            return spEL;
        }
    }

    private String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) {
        StringBuffer strBuff = new StringBuffer();
        strBuff.append(exceptionName).append(":").append(exceptionMessage).append("\n\t");
        StackTraceElement[] elements1 = elements;
        int length = elements.length;

        for(int i = 0; i < length; ++i) {
            StackTraceElement stet = elements1[i];
            strBuff.append(stet + "\n");
        }

        return strBuff.toString();
    }
}
